﻿using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace System.Windows.Controls.WpfPropertyGrid
{
  using Design;
  using Controls;
  /// <summary>
  /// PropertyGrid control.
  /// </summary>
  public partial class PropertyGrid : Control, INotifyPropertyChanged
  {
    #region INotifyPropertyChanged Members

    /// <summary>
    /// Occurs when a property value changes.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Called when property value changes.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    protected virtual void OnPropertyChanged(string propertyName)
    {
      var handler = PropertyChanged;
      if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region Static fields

    private static readonly Type ThisType = typeof(PropertyGrid);
    private static Attribute[] DefaultPropertiesFilter = new Attribute[] { new PropertyFilterAttribute(PropertyFilterOptions.SetValues | PropertyFilterOptions.UnsetValues | PropertyFilterOptions.Valid) };

    #endregion

    #region Private members

    private void DoReload()
    {
      if (SelectedObject == null)
      {
        Categories = new GridEntryCollection<CategoryItem>();
        Properties = new GridEntryCollection<PropertyItem>();
      }
      else
      {
        // Collect BrowsableCategoryAttribute items
        var categoryAttributes = PropertyGridUtils.GetAttributes<BrowsableCategoryAttribute>(SelectedObject);
        browsableCategories = new List<BrowsableCategoryAttribute>(categoryAttributes);

        // Collect BrowsablePropertyAttribute items
        var propertyAttributes = PropertyGridUtils.GetAttributes<BrowsablePropertyAttribute>(SelectedObject);
        browsableProperties = new List<BrowsablePropertyAttribute>(propertyAttributes);

        // Collect categories and properties
        var properties = CollectProperties(currentObjects);

        // TODO: This needs more elegant implementation
        var categories = new GridEntryCollection<CategoryItem>(CollectCategories(properties));
        if (_categories != null && _categories.Count > 0)
          CopyCategoryFrom(_categories, categories);

        Categories = categories; //new CategoryCollection(CollectCategories(properties));
        Properties = new GridEntryCollection<PropertyItem>(properties);

      }
    }

    private static void CopyCategoryFrom(GridEntryCollection<CategoryItem> oldValue, IEnumerable<CategoryItem> newValue)
    {
      foreach (var category in newValue)
      {
        var prev = oldValue[category.Name];
        if (prev == null) continue;

        category.IsExpanded = prev.IsExpanded;
      }
    }

    private void OnPropertyItemValueChanged(PropertyItem property, object oldValue, object newValue)
    {
      RaisePropertyValueChangedEvent(property, oldValue);
    }

    private void HookPropertyChanged(PropertyItem item)
    {
      if (item == null) return;
      item.ValueChanged += OnPropertyItemValueChanged;
    }

    private void UnhookPropertyChanged(PropertyItem item)
    {
      if (item == null) return;
      item.ValueChanged -= OnPropertyItemValueChanged;
    }

    #endregion

    #region Fields

    private List<BrowsablePropertyAttribute> browsableProperties = new List<BrowsablePropertyAttribute>();
    private List<BrowsableCategoryAttribute> browsableCategories = new List<BrowsableCategoryAttribute>();

    private object[] currentObjects;

    #endregion

    #region Events

    #region PropertyEditingStarted Event (Bubble)

    #endregion PropertyEditingStarted event (Bubble)

    #region PropertyEditingFinished Event (Bubble)

    #endregion PropertyEditingFinished Event (Bubble)

    #region PropertyValueChanged Event (Bubble)

    #endregion PropertyValueChanged event (Bubble)

    #region SelectedObjectsChanged

    #endregion

    #endregion

    #region Properties

    #region ItemsBackground

    #endregion

    #region ItemsForeground

    #endregion

    private EditorCollection _Editors = new EditorCollection();
    /// <summary>
    /// Gets the editors collection.
    /// </summary>
    /// <value>The editors collection.</value>
    public EditorCollection Editors
    {
      get { return _Editors; }
    }



    #region SelectedObject

    #endregion

    #region SelectedObjects

    #endregion

    #region Properties

    #endregion

    /// <summary>
    /// Gets a value indicating whether this instance has properties.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if this instance has properties; otherwise, <c>false</c>.
    /// </value>
    public bool HasProperties
    {
      get { return _properties != null && _properties.Count > 0; }
    }

    #region PropertyComparer

    #endregion

    #region CategoryComparer

    #endregion

    #region Categories

    #endregion

    /// <summary>
    /// Gets a value indicating whether this instance has categories.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if this instance has categories; otherwise, <c>false</c>.
    /// </value>
    public bool HasCategories
    {
      get { return _categories != null && _categories.Count > 0; }
    }

    #region ShowReadOnlyProperties property

    #endregion

    #region ShowAttachedProperties property

    #endregion

    #region PropertyFilter

    #endregion

    #region PropertyFilterVisibility

    #endregion

    #region PropertyDisplayMode

    #endregion

    #endregion

    #region ctor
    static PropertyGrid()
    {
      DefaultStyleKeyProperty.OverrideMetadata(ThisType, new FrameworkPropertyMetadata(ThisType));
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="PropertyGrid"/> class.
    /// </summary>
    public PropertyGrid()
    {
      EventManager.RegisterClassHandler(typeof(PropertyGrid), GotFocusEvent, new RoutedEventHandler(ShowDescription), true);

      // Assign Layout to be Alphabetical by default
      Layout = new AlphabeticalLayout();

      // Wire command bindings
      InitializeCommandBindings();

    }

    private void ShowDescription(object sender, RoutedEventArgs e)
    {
      if (e.OriginalSource == null || !(e.OriginalSource is FrameworkElement) ||
          (e.OriginalSource as FrameworkElement).DataContext == null ||
          !((e.OriginalSource as FrameworkElement).DataContext is PropertyItemValue) ||
          ((e.OriginalSource as FrameworkElement).DataContext as PropertyItemValue).ParentProperty == null)
        return;
      var descri = ((e.OriginalSource as FrameworkElement).DataContext as PropertyItemValue).ParentProperty.ToolTip;
      CurrentDescription = descri == null ? "" : descri.ToString();
    }

    #endregion

    #region CurrentDescription

    /// <summary>
    /// CurrentDescription Dependency Property
    /// </summary>
    public static readonly DependencyProperty CurrentDescriptionProperty =
        DependencyProperty.Register("CurrentDescription", typeof(string), typeof(PropertyGrid),
            new FrameworkPropertyMetadata((string)"",
                new PropertyChangedCallback(OnCurrentDescriptionChanged)));

    /// <summary>
    /// Gets or sets the CurrentDescription property.  
    /// </summary>
    public string CurrentDescription
    {
      get { return (string)GetValue(CurrentDescriptionProperty); }
      set { SetValue(CurrentDescriptionProperty, value); }
    }

    /// <summary>
    /// Handles changes to the CurrentDescription property.
    /// </summary>
    private static void OnCurrentDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
      ((PropertyGrid)d).OnCurrentDescriptionChanged(e);
    }

    /// <summary>
    /// Provides derived classes an opportunity to handle changes to the CurrentDescription property.
    /// </summary>
    protected virtual void OnCurrentDescriptionChanged(DependencyPropertyChangedEventArgs e)
    {
    }

    #endregion


    /// <summary>
    /// Identifies the <see cref="PropertyEditingStarted"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PropertyEditingStartedEvent =
      EventManager.RegisterRoutedEvent("PropertyEditingStarted", RoutingStrategy.Bubble, typeof(PropertyEditingEventHandler), ThisType);

    /// <summary>
    /// Occurs when property editing is started.
    /// </summary>
    /// <remarks>
    /// This event is intended to be used in customization scenarios. It is not used by PropertyGrid control directly.
    /// </remarks>
    public event RoutedEventHandler PropertyEditingStarted
    {
      add { AddHandler(PropertyEditingStartedEvent, value); }
      remove { RemoveHandler(PropertyEditingStartedEvent, value); }
    }

    /// <summary>
    /// Identifies the <see cref="PropertyEditingFinished"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PropertyEditingFinishedEvent =
      EventManager.RegisterRoutedEvent("PropertyEditingFinished", RoutingStrategy.Bubble, typeof(PropertyEditingEventHandler), ThisType);

    /// <summary>
    /// Occurs when property editing is finished.
    /// </summary>
    /// <remarks>
    /// This event is intended to be used in customization scenarios. It is not used by PropertyGrid control directly.
    /// </remarks>
    public event RoutedEventHandler PropertyEditingFinished
    {
      add { AddHandler(PropertyEditingFinishedEvent, value); }
      remove { RemoveHandler(PropertyEditingFinishedEvent, value); }
    }

    /// <summary>
    /// Identifies the <see cref="PropertyValueChanged"/> routed event.
    /// </summary>
    public static readonly RoutedEvent PropertyValueChangedEvent =
      EventManager.RegisterRoutedEvent("PropertyValueChanged", RoutingStrategy.Bubble, typeof(PropertyValueChangedEventHandler), ThisType);

    /// <summary>
    /// Occurs when property item value is changed.
    /// </summary>
    public event PropertyValueChangedEventHandler PropertyValueChanged
    {
      add { AddHandler(PropertyValueChangedEvent, value); }
      remove { RemoveHandler(PropertyValueChangedEvent, value); }
    }

    private void RaisePropertyValueChangedEvent(PropertyItem property, object oldValue)
    {
      var args = new PropertyValueChangedEventArgs(PropertyValueChangedEvent, property, oldValue);
      RaiseEvent(args);
    }

    /// <summary>
    /// Occurs when selected objects are changed.
    /// </summary>
    public event EventHandler SelectedObjectsChanged;

    /// <summary>
    /// Called when selected objects were changed.
    /// </summary>
    protected virtual void OnSelectedObjectsChanged()
    {
      var handler = SelectedObjectsChanged;
      if (handler != null) handler(this, EventArgs.Empty);
    }

    /// <summary>
    /// Identifies the <see cref="ItemsBackground"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ItemsBackgroundProperty =
      DependencyProperty.Register("ItemsBackground", typeof(Brush), ThisType, new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None));

    /// <summary>
    /// Gets or sets the brush for items background. This is a dependency property.
    /// </summary>
    /// <value>The items background brush.</value>
    public Brush ItemsBackground
    {
      get { return (Brush)GetValue(ItemsBackgroundProperty); }
      set { SetValue(ItemsBackgroundProperty, value); }
    }

    /// <summary>
    /// Identifies the <see cref="ItemsForeground"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ItemsForegroundProperty =
      DependencyProperty.Register("ItemsForeground", typeof(Brush), ThisType, new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.None));

    /// <summary>
    /// Gets or sets the items foreground brush. This is a dependency property.
    /// </summary>
    /// <value>The items foreground brush.</value>
    public Brush ItemsForeground
    {
      get { return (Brush)GetValue(ItemsForegroundProperty); }
      set { SetValue(ItemsForegroundProperty, value); }
    }

    /// <summary>
    /// Identifies the <see cref="Layout"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty LayoutProperty =
     DependencyProperty.Register("Layout", typeof(Control), ThisType,
     new FrameworkPropertyMetadata(default(AlphabeticalLayout), FrameworkPropertyMetadataOptions.AffectsArrange | FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnLayoutChanged));

    private static void OnLayoutChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      var layoutContainer = e.NewValue as Control;
      if (layoutContainer != null)
        layoutContainer.DataContext = sender;
    }

    /// <summary>
    /// Gets or sets the layout to be used to display properties.
    /// </summary>
    /// <value>The layout to be used to display properties.</value>
    public Control Layout
    {
      get { return (Control)GetValue(LayoutProperty); }
      set { SetValue(LayoutProperty, value); }
    }


    /// <summary>
    /// Gets or sets the selected object.
    /// </summary>
    /// <value>The selected object.</value>
    public object SelectedObject
    {
      get { return (currentObjects != null && currentObjects.Length != 0) ? currentObjects[0] : null; }
      set { SelectedObjects = (value == null) ? new object[0] : new[] { value }; }
    }

    /// <summary>
    /// Gets or sets the selected objects.
    /// </summary>
    /// <value>The selected objects.</value>
    public object[] SelectedObjects
    {
      get { return (currentObjects == null) ? new object[0] : (object[])currentObjects.Clone(); }
      set
      {
        // Ensure there are no nulls in the array
        VerifySelectedObjects(value);

        var sameSelection = false;

        // Check whether new selection is the same as was previously defined
        if (currentObjects != null && value != null && currentObjects.Length == value.Length)
        {
          sameSelection = true;

          for (var i = 0; i < value.Length && sameSelection; i++)
          {
            if (currentObjects[i] != value[i])
              sameSelection = false;
          }
        }

        if (!sameSelection)
        {
          // Assign new objects and reload
          if (value == null)
          {
            currentObjects = new object[0];
            DoReload();
          }
          else
          {
            // process single selection
            if (value.Length == 1 && currentObjects != null && currentObjects.Length == 1)
            {
              var oldValue = (currentObjects != null && currentObjects.Length > 0) ? currentObjects[0] : null;
              var newValue = (value.Length > 0) ? value[0] : null;

              currentObjects = (object[])value.Clone();

              if (oldValue != null && newValue != null && oldValue.GetType().Equals(newValue.GetType()))
                SwapSelectedObject(newValue);
              else
              {
                DoReload();
              }
            }
            // process multiple selection
            else
            {
              currentObjects = (object[])value.Clone();
              DoReload();
            }
          }

          OnPropertyChanged("SelectedObjects");
          OnPropertyChanged("SelectedObject");
          OnSelectedObjectsChanged();
        }
        else
        {
          // TODO: Swap multiple objects here? Guess nothing can be done in this case...
        }
      }
    }

    private GridEntryCollection<PropertyItem> _properties;
    /// <summary>
    /// Gets or sets the properties of the selected object(s).
    /// </summary>
    /// <value>The properties of the selected object(s).</value>
    public GridEntryCollection<PropertyItem> Properties
    {
      get { return _properties; }
      private set
      {
        if (_properties == value) return;

        if (_properties != null)
        {
          foreach (var item in _properties)
          {
            UnhookPropertyChanged(item);
            item.Dispose();
          }
        }

        if (value != null)
        {
          _properties = value;

          if (PropertyComparer != null)
            _properties.Sort(PropertyComparer);

          foreach (var item in _properties)
            HookPropertyChanged(item);
        }

        OnPropertyChanged("Properties");
        OnPropertyChanged("HasProperties");
      }
    }

    private IComparer<PropertyItem> _propertyComparer;

    /// <summary>
    /// Gets or sets the default property comparer.
    /// </summary>
    /// <value>The default property comparer.</value>
    public IComparer<PropertyItem> PropertyComparer
    {
      get { return _propertyComparer ?? (_propertyComparer = new PropertyItemComparer()); }
      set
      {
        if (_propertyComparer == value) return;
        _propertyComparer = value;

        if (_properties != null)
          _properties.Sort(_propertyComparer);

        OnPropertyChanged("PropertyComparer");
      }
    }

    private IComparer<CategoryItem> _categoryComparer;

    /// <summary>
    /// Gets or sets the default category comparer.
    /// </summary>
    /// <value>The default category comparer.</value>
    public IComparer<CategoryItem> CategoryComparer
    {
      get { return _categoryComparer ?? (_categoryComparer = new CategoryItemComparer()); }
      set
      {
        if (_categoryComparer == value) return;
        _categoryComparer = value;

        if (_categories != null)
          _categories.Sort(_categoryComparer);

        OnPropertyChanged("Categories");
      }
    }

    private GridEntryCollection<CategoryItem> _categories;
    /// <summary>
    /// Gets or sets the categories of the selected object(s).
    /// </summary>
    /// <value>The categories of the selected object(s).</value>
    public GridEntryCollection<CategoryItem> Categories
    {
      get { return _categories; }
      private set
      {
        if (_categories == value) return;
        _categories = value;

        if (CategoryComparer != null)
          _categories.Sort(CategoryComparer);

        OnPropertyChanged("Categories");
        OnPropertyChanged("HasCategories");
      }
    }

    /// <summary>
    /// Identifies the <see cref="ShowReadOnlyProperties"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ShowReadOnlyPropertiesProperty =
     DependencyProperty.Register("ShowReadOnlyProperties", typeof(bool), ThisType,
     new PropertyMetadata(false, OnShowReadOnlyPropertiesChanged));

    private static void OnShowReadOnlyPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      var pg = (PropertyGrid)sender;

      // Check whether any object was selected
      if (pg.SelectedObject == null) return;

      // Check whether categories or properties were created
      if (pg.HasCategories | pg.HasProperties) pg.DoReload();
    }

    /// <summary>
    /// Gets or sets a value indicating whether read-only properties should be displayed. This is a dependency property.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if read-only properties should be displayed; otherwise, <c>false</c>. Default is <c>false</c>.
    /// </value>
    public bool ShowReadOnlyProperties
    {
      get { return (bool)GetValue(ShowReadOnlyPropertiesProperty); }
      set { SetValue(ShowReadOnlyPropertiesProperty, value); }
    }

    /// <summary>
    /// Identifies the <see cref="ShowAttachedProperties"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty ShowAttachedPropertiesProperty =
      DependencyProperty.Register("ShowAttachedProperties", typeof(bool), ThisType,
      new PropertyMetadata(false, OnShowAttachedPropertiesChanged));

    private static void OnShowAttachedPropertiesChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      var pg = (PropertyGrid)sender;
      if (pg.SelectedObject == null) return;
      if (pg.HasCategories | pg.HasProperties) pg.DoReload();
    }

    /// <summary>
    /// Gets or sets a value indicating whether attached properties should be displayed.
    /// </summary>
    /// <value>
    /// 	<c>true</c> if attached properties should be displayed; otherwise, <c>false</c>. Default is <c>false</c>.
    /// </value>
    public bool ShowAttachedProperties
    {
      get { return (bool)GetValue(ShowAttachedPropertiesProperty); }
      set { SetValue(ShowAttachedPropertiesProperty, value); }
    }

    /// <summary>
    /// Identifies the <see cref="PropertyFilter"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertyFilterProperty =
      DependencyProperty.Register("PropertyFilter", typeof(string), ThisType,
      new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnPropertyFilterChanged));

    private static void OnPropertyFilterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      var propertyGrid = (PropertyGrid)sender;

      if (propertyGrid.SelectedObject == null || !propertyGrid.HasCategories) return;

      foreach (var category in propertyGrid.Categories)
        category.ApplyFilter(new PropertyFilter(propertyGrid.PropertyFilter));
    }

    /// <summary>
    /// Gets or sets the property filter. This is a dependency property.
    /// </summary>
    /// <value>The property filter.</value>
    public string PropertyFilter
    {
      get { return (string)GetValue(PropertyFilterProperty); }
      set { SetValue(PropertyFilterProperty, value); }
    }

    /// <summary>
    /// Identifies the <see cref="PropertyFilterVisibility"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertyFilterVisibilityProperty =
      DependencyProperty.Register("PropertyFilterVisibility", typeof(Visibility), ThisType,
      new FrameworkPropertyMetadata(Visibility.Visible));


    /// <summary>
    /// Gets or sets the property filter visibility state.
    /// </summary>
    /// <value>The property filter visibility state.</value>
    public Visibility PropertyFilterVisibility
    {
      get { return (Visibility)GetValue(PropertyFilterVisibilityProperty); }
      set { SetValue(PropertyFilterVisibilityProperty, value); }
    }

    /// <summary>
    /// Identifies the <see cref="PropertyDisplayMode"/> dependency property.
    /// </summary>
    public static readonly DependencyProperty PropertyDisplayModeProperty =
      DependencyProperty.Register("PropertyDisplayMode", typeof(PropertyDisplayMode), ThisType,
      new FrameworkPropertyMetadata(PropertyDisplayMode.All, new PropertyChangedCallback(OnPropertyDisplayModePropertyChanged)));

    /// <summary>
    /// Gets or sets the property display mode. This is a dependency property.
    /// </summary>
    /// <value>The property display mode.</value>
    public PropertyDisplayMode PropertyDisplayMode
    {
      get { return (PropertyDisplayMode)GetValue(PropertyDisplayModeProperty); }
      set { SetValue(PropertyDisplayModeProperty, value); }
    }

    private static void OnPropertyDisplayModePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
      var propertyGrid = (PropertyGrid)sender;
      if (propertyGrid.SelectedObject == null) return;
      propertyGrid.DoReload();
    }

    #region Internal API

    internal CategoryItem CreateCategory(CategoryAttribute attribute)
    {
      // Check the attribute argument to be passed
      Debug.Assert(attribute != null);
      if (attribute == null) return null;

      // Check browsable restrictions
      //if (!ShouldDisplayCategory(attribute.Category)) return null;

      // Create a new CategoryItem
      var categoryItem = new CategoryItem(this, attribute);
      categoryItem.IsBrowsable = ShouldDisplayCategory(categoryItem.Name);

      // Return resulting item
      return categoryItem;
    }

    private PropertyItem CreatePropertyItem(PropertyDescriptor descriptor)
    {
      // Check browsable restrictions
      //if (!ShoudDisplayProperty(descriptor)) return null;

      var dpDescriptor = DependencyPropertyDescriptor.FromProperty(descriptor);
      // Provide additional checks for dependency properties
      if (dpDescriptor != null)
      {
        // Check whether dependency properties are not prohibited
        if (PropertyDisplayMode == PropertyDisplayMode.Native) return null;

        // Check whether attached properties are to be displayed
        if (dpDescriptor.IsAttached && !ShowAttachedProperties) return null;
      }
      else
      {
        if (PropertyDisplayMode == PropertyDisplayMode.Dependency) return null;
      }

      // Check whether readonly properties are to be displayed
      if (descriptor.IsReadOnly && !ShowReadOnlyProperties) return null;

      // Note: superceded by ShouldDisplayProperty method call
      // Check whether property is browsable and add it to the collection
      // if (!descriptor.IsBrowsable) return null;

      //PropertyItem item = new PropertyItem(this, this.SelectedObject, descriptor);      

      var item = (currentObjects.Length > 1)
        ? new PropertyItem(this, currentObjects, descriptor)
        : new PropertyItem(this, SelectedObject, descriptor);

      //item.OverrideIsBrowsable(new bool?(ShoudDisplayProperty(descriptor)));
      item.IsBrowsable = ShoudDisplayProperty(descriptor);

      return item;
    }

    private bool ShoudDisplayProperty(PropertyDescriptor propertyDescriptor)
    {
      Debug.Assert(propertyDescriptor != null);
      if (propertyDescriptor == null) return false;

      // Check whether owning category is not restricted to ouput
      var showWithinCategory = ShouldDisplayCategory(propertyDescriptor.Category);
      if (!showWithinCategory) return false;

      // Check the explicit declaration
      var attribute = browsableProperties.FirstOrDefault(item => item.PropertyName == propertyDescriptor.Name);
      if (attribute != null) return attribute.Browsable;

      // Check the wildcard
      var wildcard = browsableProperties.FirstOrDefault(item => item.PropertyName == BrowsablePropertyAttribute.All);
      if (wildcard != null) return wildcard.Browsable;

      // Return default/standard Browsable settings for the property
      return propertyDescriptor.IsBrowsable;
    }

    private bool ShouldDisplayCategory(string categoryName)
    {
      if (string.IsNullOrEmpty(categoryName)) return false;

      // Check the explicit declaration
      var attribute = browsableCategories.FirstOrDefault(item => item.CategoryName == categoryName);
      if (attribute != null) return attribute.Browsable;

      // Check the wildcard
      var wildcard = browsableCategories.FirstOrDefault(item => item.CategoryName == BrowsableCategoryAttribute.All);
      if (wildcard != null) return wildcard.Browsable;

      // Allow by default if no restrictions were applied
      return true;
    }

    #endregion

    /// <summary>
    /// Gets the editor for a grid entry.
    /// </summary>
    /// <param name="entry">The entry to look the editor for.</param>
    /// <returns>Editor for the entry</returns>
    public virtual Editor GetEditor(GridEntry entry)
    {
      var property = entry as PropertyItem;
      if (property != null)
        return Editors.GetEditor(property);

      var category = entry as CategoryItem;
      if (category != null)
        return Editors.GetEditor(category);

      return null;
    }

    private void SwapSelectedObject(object value)
    {
      //foreach (PropertyItem property in this.Properties)
      //{
      //  property.SetPropertySouce(value);
      //}
      DoReload();
    }

    private IEnumerable<CategoryItem> CollectCategories(IEnumerable<PropertyItem> properties)
    {
      var categories = new Dictionary<string, CategoryItem>();
      var refusedCategories = new HashSet<string>();

      foreach (var property in properties)
      {
        if (refusedCategories.Contains(property.CategoryName)) continue;
        CategoryItem category;

        if (categories.ContainsKey(property.CategoryName))
          category = categories[property.CategoryName];
        else
        {
          category = CreateCategory(property.GetAttribute<CategoryAttribute>());

          if (category == null)
          {
            refusedCategories.Add(property.CategoryName);
            continue;
          }

          categories[category.Name] = category;
        }

        category.AddProperty(property);
      }

      return categories.Values.ToList();
    }

    private IEnumerable<PropertyItem> CollectProperties(object[] components)
    {
      if (components == null || components.Length == 0) throw new ArgumentNullException("components");

      // This is an obsolete code left for performance improvements demo. Will be removed in the future versions.
      /*
      var descriptors = component.Length == 1
        ? TypeDescriptor.GetProperties(component[0], DefaultPropertiesFilter).OfType<PropertyDescriptor>()
        : ObjectServices.GetMergedProperties(component);
      */

      // TODO: PropertyItem is to be wired with PropertyData rather than pure PropertyDescriptor in the next version!
      var descriptors = (components.Length == 1)
        ? MetadataRepository.GetProperties(components[0]).Select(prop => prop.Descriptor)
        : ObjectServices.GetMergedProperties(components);

      IList<PropertyItem> propertyCollection = new List<PropertyItem>();

      foreach (var propertyDescriptor in descriptors)
      // This is an obsolete code left for performance improvements demo. Will be removed in the future versions.
      //CollectProperties(component, propertyDescriptor, propertyCollection);
      {
        var item = CreatePropertyItem(propertyDescriptor);
        if (item != null) propertyCollection.Add(item);
      }

      return propertyCollection;
    }

    // This is an obsolete code left for performance improvements demo. Will be removed in the future versions.
    /*
    private void CollectProperties(object component, PropertyDescriptor descriptor, IList<PropertyItem> propertyList)
    {
      if (descriptor.Attributes[typeof(FlatternHierarchyAttribute)] == null)
      {
        PropertyItem item = CreatePropertyItem(descriptor);
        if (item != null)
          propertyList.Add(item);
      }
      else
      {
        component = descriptor.GetValue(component);
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(component);
        foreach (PropertyDescriptor propertyDescriptor in properties)
          CollectProperties(component, propertyDescriptor, propertyList);
      }
    }
    */

    private static void VerifySelectedObjects(object[] value)
    {
      if (value != null && value.Length > 0)
      {
        // Ensure there are no nulls in the array
        for (var i = 0; i < value.Length; i++)
        {
          if (value[i] == null)
          {
            var args = new object[] { i.ToString(CultureInfo.CurrentCulture), value.Length.ToString(CultureInfo.CurrentCulture) };
            // TODO: Move exception format to resources/settings!
            throw new ArgumentNullException(string.Format("Item {0} in the 'objs' array is null. The array must begin with at least {1} members.", args));
          }
        }
      }
    }
    /// <summary>
    /// Invoked when an unhandled <see cref="UIElement.KeyDown"/> attached event reaches an element in its route that is derived from this class. Implement this method to add class handling for this event.
    /// </summary>
    /// <param name="e">The <see cref="T:System.Windows.Input.KeyEventArgs"/> that contains the event data.</param>
    protected override void OnKeyDown(KeyEventArgs e)
    {
      if (e.Key == Key.Tab && e.OriginalSource is DependencyObject)//tabbing over the property editors
      {
        var source = e.OriginalSource as DependencyObject;
        var element = Keyboard.Modifiers == ModifierKeys.Shift ? GetTabElement(source, -1) : GetTabElement(source, 1);
        if (element != null)
        {
          element.Focus();
          e.Handled = true;
          return;
        }
      }

      base.OnKeyDown(e);

    }

    /// <summary>
    /// Gets the tab element on which the focus can be placed.
    /// </summary>
    /// <remarks>
    /// If an element is not enabled it will not be returned.
    /// </remarks>
    /// <param name="source">The source.</param>
    /// <param name="delta">The delta.</param>
    private UIElement GetTabElement(DependencyObject source, int delta)
    {
      if (source == null) return null;
      PropertyContainer container = null;
      if (source is SearchTextBox && HasCategories)
      {
        var itemspres = FindVisualChild<ItemsPresenter>(this);
        if (itemspres != null)
        {
          var catcontainer = FindVisualChild<CategoryContainer>(itemspres);
          if (catcontainer != null)
          {
            container = FindVisualChild<PropertyContainer>(catcontainer);
          }
        }
      }
      else
        container = FindVisualParent<PropertyContainer>(source);

      var spanel = FindVisualParent<StackPanel>(container);
      if (spanel != null && spanel.Children.Contains(container))
      {
        var index = spanel.Children.IndexOf(container);
        if (delta > 0)
          index = (index == spanel.Children.Count - 1) ? 0 : index + delta;//go back to the first after last
        else
          index = (index == 0) ? spanel.Children.Count - 1 : index + delta;//go to last after first
        //loop inside the list
        if (index < 0)
          index = spanel.Children.Count - 1;
        if (index >= spanel.Children.Count)
          index = 0;


        var next = VisualTreeHelper.GetChild(spanel, index) as PropertyContainer;//this has always a Grid as visual child

        var grid = FindVisualChild<Grid>(next);
        if (grid != null && grid.Children.Count > 1)
        {
          var pecp = grid.Children[1] as PropertyEditorContentPresenter;
          var final = VisualTreeHelper.GetChild(pecp, 0);
          if ((final as UIElement).IsEnabled && (final as UIElement).Focusable && !(next.DataContext as PropertyItem).IsReadOnly)
            return final as UIElement;
          else
            return GetTabElement(final, delta);

        }

      }
      return null;
    }

    private static T FindVisualParent<T>(DependencyObject element) where T : class
    {
      if (element == null) return default(T);
      object parent = VisualTreeHelper.GetParent(element);
      if (parent is T)
        return parent as T;
      if (parent is DependencyObject)
        return FindVisualParent<T>(parent as DependencyObject);
      return null;
    }
    private static T FindVisualChild<T>(DependencyObject element) where T : class
    {
      if (element == null) return default(T);
      if (element is T) return element as T;
      if (VisualTreeHelper.GetChildrenCount(element) > 0)
      {
        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
        {

          object child = VisualTreeHelper.GetChild(element, i);
          if (child is SearchTextBox) continue;//speeds up things a bit
          if (child is T)
            return child as T;
          if (child is DependencyObject)
          {
            var res = FindVisualChild<T>(child as DependencyObject);
            if (res == null) continue;
            return res;
          }
        }
      }
      return null;
    }

  }
}






